/*
 * File    : DownloadImpl.java
 * Created : 06-Jan-2004
 * By      : parg
 * 
 * Azureus - a Java Bittorrent client
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details ( see the LICENSE file ).
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package org.gudy.azureus2.pluginsimpl.local.download;

/**
 * @author parg
 *
 */

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.gudy.azureus2.core3.category.Category;
import org.gudy.azureus2.core3.category.CategoryManager;
import org.gudy.azureus2.core3.download.DownloadManager;
import org.gudy.azureus2.core3.download.DownloadManagerActivationListener;
import org.gudy.azureus2.core3.download.DownloadManagerException;
import org.gudy.azureus2.core3.download.DownloadManagerListener;
import org.gudy.azureus2.core3.download.DownloadManagerPeerListener;
import org.gudy.azureus2.core3.download.DownloadManagerState;
import org.gudy.azureus2.core3.download.DownloadManagerStateAttributeListener;
import org.gudy.azureus2.core3.download.DownloadManagerStateEvent;
import org.gudy.azureus2.core3.download.DownloadManagerStateListener;
import org.gudy.azureus2.core3.download.DownloadManagerTrackerListener;
import org.gudy.azureus2.core3.download.impl.DownloadManagerMoveHandler;
import org.gudy.azureus2.core3.global.GlobalManager;
import org.gudy.azureus2.core3.global.GlobalManagerDownloadRemovalVetoException;
import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.logging.LogRelation;
import org.gudy.azureus2.core3.peer.PEPeer;
import org.gudy.azureus2.core3.peer.PEPeerManager;
import org.gudy.azureus2.core3.peer.PEPeerSource;
import org.gudy.azureus2.core3.torrent.TOTorrent;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncer;
import org.gudy.azureus2.core3.tracker.client.TRTrackerAnnouncerResponse;
import org.gudy.azureus2.core3.tracker.client.TRTrackerScraperResponse;
import org.gudy.azureus2.core3.util.AEMonitor;
import org.gudy.azureus2.core3.util.BEncoder;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.SystemTime;
import org.gudy.azureus2.plugins.ddb.DistributedDatabase;
import org.gudy.azureus2.plugins.disk.DiskManager;
import org.gudy.azureus2.plugins.disk.DiskManagerFileInfo;
import org.gudy.azureus2.plugins.download.Download;
import org.gudy.azureus2.plugins.download.DownloadActivationEvent;
import org.gudy.azureus2.plugins.download.DownloadActivationListener;
import org.gudy.azureus2.plugins.download.DownloadAnnounceResult;
import org.gudy.azureus2.plugins.download.DownloadAnnounceResultPeer;
import org.gudy.azureus2.plugins.download.DownloadAttributeListener;
import org.gudy.azureus2.plugins.download.DownloadCompletionListener;
import org.gudy.azureus2.plugins.download.DownloadException;
import org.gudy.azureus2.plugins.download.DownloadListener;
import org.gudy.azureus2.plugins.download.DownloadPeerListener;
import org.gudy.azureus2.plugins.download.DownloadPropertyEvent;
import org.gudy.azureus2.plugins.download.DownloadPropertyListener;
import org.gudy.azureus2.plugins.download.DownloadRemovalVetoException;
import org.gudy.azureus2.plugins.download.DownloadScrapeResult;
import org.gudy.azureus2.plugins.download.DownloadStats;
import org.gudy.azureus2.plugins.download.DownloadStub;
import org.gudy.azureus2.plugins.download.DownloadTrackerListener;
import org.gudy.azureus2.plugins.download.DownloadWillBeRemovedListener;
import org.gudy.azureus2.plugins.download.savelocation.SaveLocationChange;
import org.gudy.azureus2.plugins.network.RateLimiter;
import org.gudy.azureus2.plugins.peers.PeerManager;
import org.gudy.azureus2.plugins.torrent.Torrent;
import org.gudy.azureus2.plugins.torrent.TorrentAttribute;
import org.gudy.azureus2.pluginsimpl.local.ddb.DDBaseImpl;
import org.gudy.azureus2.pluginsimpl.local.deprecate.PluginDeprecation;
import org.gudy.azureus2.pluginsimpl.local.disk.DiskManagerFileInfoImpl;
import org.gudy.azureus2.pluginsimpl.local.peers.PeerManagerImpl;
import org.gudy.azureus2.pluginsimpl.local.torrent.TorrentImpl;
import org.gudy.azureus2.pluginsimpl.local.torrent.TorrentManagerImpl;
import org.gudy.azureus2.pluginsimpl.local.utils.UtilitiesImpl;

import com.aelitis.azureus.core.tracker.TrackerPeerSource;
import com.aelitis.azureus.core.tracker.TrackerPeerSourceAdapter;
import com.aelitis.azureus.core.util.CopyOnWriteList;
import com.aelitis.azureus.core.util.CopyOnWriteMap;

public class DownloadImpl extends LogRelation implements Download, DownloadManagerListener, DownloadManagerTrackerListener, DownloadManagerStateListener, DownloadManagerActivationListener, DownloadManagerStateAttributeListener {
    private final DownloadManagerImpl manager;
    private final DownloadManager download_manager;
    private final DownloadStatsImpl download_stats;

    private int latest_state = ST_STOPPED;
    private boolean latest_forcedStart;

    private DownloadAnnounceResultImpl last_announce_result = new DownloadAnnounceResultImpl(this, null);
    private DownloadScrapeResultImpl last_scrape_result = new DownloadScrapeResultImpl(this, null);
    private AggregateScrapeResult last_aggregate_scrape = new AggregateScrapeResult(this);

    private TorrentImpl torrent = null;

    private List listeners = new ArrayList();
    private AEMonitor listeners_mon = new AEMonitor("Download:L");
    private List property_listeners = new ArrayList();
    private List tracker_listeners = new ArrayList();
    private AEMonitor tracker_listeners_mon = new AEMonitor("Download:TL");
    private List removal_listeners = new ArrayList();
    private AEMonitor removal_listeners_mon = new AEMonitor("Download:RL");
    private Map peer_listeners = new HashMap();
    private AEMonitor peer_listeners_mon = new AEMonitor("Download:PL");

    private CopyOnWriteList completion_listeners = new CopyOnWriteList();

    private CopyOnWriteMap read_attribute_listeners_map_cow = new CopyOnWriteMap();
    private CopyOnWriteMap write_attribute_listeners_map_cow = new CopyOnWriteMap();

    private CopyOnWriteList activation_listeners = new CopyOnWriteList();
    private DownloadActivationEvent activation_state;

    private Map<String, int[]> announce_response_map;

    protected DownloadImpl(DownloadManagerImpl _manager, DownloadManager _dm) {
        manager = _manager;
        download_manager = _dm;
        download_stats = new DownloadStatsImpl(download_manager);

        activation_state = new DownloadActivationEvent() {
            public Download getDownload() {
                return (DownloadImpl.this);
            }

            public int getActivationCount() {
                return (download_manager.getActivationCount());
            }
        };

        download_manager.addListener(this);

        latest_forcedStart = download_manager.isForceStart();
    }

    // Not available to plugins
    public DownloadManager getDownload() {
        return (download_manager);
    }

    public int getState() {
        return (convertState(download_manager.getState()));
    }

    public int getSubState() {
        int state = getState();

        if (state == ST_STOPPING) {

            int substate = download_manager.getSubState();

            if (substate == DownloadManager.STATE_QUEUED) {

                return (ST_QUEUED);

            } else if (substate == DownloadManager.STATE_STOPPED) {

                return (ST_STOPPED);

            } else if (substate == DownloadManager.STATE_ERROR) {

                return (ST_ERROR);
            }
        }

        return (state);
    }

    protected int convertState(int dm_state) {
        // dm states: waiting -> initialising -> initialized ->
        // disk states: allocating -> checking -> ready ->
        // dm states: downloading -> finishing -> seeding -> stopping -> stopped

        // "initialize" call takes from waiting -> initialising -> waiting (no port) or initialized (ok)
        // if initialized then disk manager runs through to ready
        // "startdownload" takes ready -> dl etc.
        // "stopIt" takes to stopped which is equiv to ready

        int our_state;

        switch (dm_state) {
            case DownloadManager.STATE_WAITING: {
                our_state = ST_WAITING;

                break;
            }
            case DownloadManager.STATE_INITIALIZING:
            case DownloadManager.STATE_INITIALIZED:
            case DownloadManager.STATE_ALLOCATING:
            case DownloadManager.STATE_CHECKING: {
                our_state = ST_PREPARING;

                break;
            }
            case DownloadManager.STATE_READY: {
                our_state = ST_READY;

                break;
            }
            case DownloadManager.STATE_DOWNLOADING:
            case DownloadManager.STATE_FINISHING: // finishing download - transit to seeding
            {
                our_state = ST_DOWNLOADING;

                break;
            }
            case DownloadManager.STATE_SEEDING: {
                our_state = ST_SEEDING;

                break;
            }
            case DownloadManager.STATE_STOPPING: {
                our_state = ST_STOPPING;

                break;
            }
            case DownloadManager.STATE_STOPPED: {
                our_state = ST_STOPPED;

                break;
            }
            case DownloadManager.STATE_QUEUED: {
                our_state = ST_QUEUED;

                break;
            }
            case DownloadManager.STATE_ERROR: {
                our_state = ST_ERROR;

                break;
            }
            default: {
                our_state = ST_ERROR;
            }
        }

        return (our_state);
    }

    public String getErrorStateDetails() {
        return (download_manager.getErrorDetails());
    }

    public long getFlags() {
        return (download_manager.getDownloadState().getFlags());
    }

    public boolean getFlag(long flag) {
        return (download_manager.getDownloadState().getFlag(flag));
    }

    public void setFlag(long flag, boolean set) {
        download_manager.getDownloadState().setFlag(flag, set);
    }

    public int getIndex() {
        GlobalManager globalManager = download_manager.getGlobalManager();
        return globalManager.getIndexOf(download_manager);
    }

    public Torrent getTorrent() {
        if (this.torrent != null) {
            return this.torrent;
        }

        TOTorrent torrent = download_manager.getTorrent();
        if (torrent == null) {
            return null;
        }
        this.torrent = new TorrentImpl(torrent);
        return this.torrent;
    }

    public void initialize()

    throws DownloadException {
        int state = download_manager.getState();

        if (state == DownloadManager.STATE_WAITING) {

            download_manager.initialize();

        } else {

            throw (new DownloadException("Download::initialize: download not waiting (state=" + state + ")"));
        }
    }

    public void start()

    throws DownloadException {
        int state = download_manager.getState();

        if (state == DownloadManager.STATE_READY) {

            download_manager.startDownload();

        } else {

            throw (new DownloadException("Download::start: download not ready (state=" + state + ")"));
        }
    }

    public void restart()

    throws DownloadException {
        int state = download_manager.getState();

        if (state == DownloadManager.STATE_STOPPED || state == DownloadManager.STATE_QUEUED) {

            download_manager.setStateWaiting();

        } else {

            throw (new DownloadException("Download::restart: download already running (state=" + state + ")"));
        }
    }

    public void stop()

    throws DownloadException {
        if (download_manager.getState() != DownloadManager.STATE_STOPPED) {

            download_manager.stopIt(DownloadManager.STATE_STOPPED, false, false);

        } else {

            throw (new DownloadException("Download::stop: download already stopped"));
        }
    }

    public void stopAndQueue()

    throws DownloadException {
        if (download_manager.getState() != DownloadManager.STATE_QUEUED) {

            download_manager.stopIt(DownloadManager.STATE_QUEUED, false, false);

        } else {

            throw (new DownloadException("Download::stopAndQueue: download already queued"));
        }
    }

    public void recheckData()

    throws DownloadException {
        if (!download_manager.canForceRecheck()) {

            throw (new DownloadException("Download::recheckData: download must be stopped, queued or in error state"));
        }

        download_manager.forceRecheck();
    }

    public boolean isStartStopLocked() {
        return (download_manager.getState() == DownloadManager.STATE_STOPPED);
    }

    public boolean isForceStart() {
        return download_manager.isForceStart();
    }

    public void setForceStart(boolean forceStart) {
        download_manager.setForceStart(forceStart);
    }

    public boolean isPaused() {
        return (download_manager.isPaused());
    }

    public void pause() {
        download_manager.pause();
    }

    public void resume() {
        download_manager.resume();
    }

    public int getPosition() {
        return download_manager.getPosition();
    }

    public long getCreationTime() {
        return (download_manager.getCreationTime());
    }

    public void setPosition(int newPosition) {
        download_manager.setPosition(newPosition);
    }

    public void moveUp() {
        download_manager.getGlobalManager().moveUp(download_manager);
    }

    public void moveDown() {
        download_manager.getGlobalManager().moveDown(download_manager);
    }

    public void moveTo(int pos) {
        download_manager.getGlobalManager().moveTo(download_manager, pos);
    }

    public String getName() {
        return download_manager.getDisplayName();
    }

    public String getTorrentFileName() {
        return download_manager.getTorrentFileName();
    }

    public String getCategoryName() {
        Category category = download_manager.getDownloadState().getCategory();
        if (category == null)
            category = CategoryManager.getCategory(Category.TYPE_UNCATEGORIZED);

        if (category == null)
            return null;
        return category.getName();
    }

    public String getAttribute(TorrentAttribute attribute) {
        String name = convertAttribute(attribute);

        if (name != null) {

            return (download_manager.getDownloadState().getAttribute(name));
        }

        return (null);
    }

    public String[] getListAttribute(TorrentAttribute attribute) {
        String name = convertAttribute(attribute);

        if (name != null) {

            return (download_manager.getDownloadState().getListAttribute(name));
        }

        return (null);
    }

    public void setListAttribute(TorrentAttribute attribute, String[] value) {
        String name = convertAttribute(attribute);

        if (name != null) {
            download_manager.getDownloadState().setListAttribute(name, value);
        }
    }

    public void setMapAttribute(TorrentAttribute attribute, Map value) {
        String name = convertAttribute(attribute);

        if (name != null) {

            // gotta clone before updating in case user has read values and then just
            // updated them - setter code optimises out sets of the same values...

            download_manager.getDownloadState().setMapAttribute(name, BEncoder.cloneMap(value));
        }
    }

    public Map getMapAttribute(TorrentAttribute attribute) {
        String name = convertAttribute(attribute);

        if (name != null) {

            return (download_manager.getDownloadState().getMapAttribute(name));
        }

        return (null);
    }

    public void setAttribute(TorrentAttribute attribute, String value) {
        String name = convertAttribute(attribute);

        if (name != null) {

            download_manager.getDownloadState().setAttribute(name, value);
        }
    }

    public boolean hasAttribute(TorrentAttribute attribute) {
        String name = convertAttribute(attribute);
        if (name == null) {
            return false;
        }
        return download_manager.getDownloadState().hasAttribute(name);
    }

    public boolean getBooleanAttribute(TorrentAttribute attribute) {
        String name = convertAttribute(attribute);
        if (name == null) {
            return false;
        } // Default value
        return download_manager.getDownloadState().getBooleanAttribute(name);
    }

    public void setBooleanAttribute(TorrentAttribute attribute, boolean value) {
        String name = convertAttribute(attribute);
        if (name != null) {
            download_manager.getDownloadState().setBooleanAttribute(name, value);
        }
    }

    public int getIntAttribute(TorrentAttribute attribute) {
        String name = convertAttribute(attribute);
        if (name == null) {
            return 0;
        } // Default value
        return download_manager.getDownloadState().getIntAttribute(name);
    }

    public void setIntAttribute(TorrentAttribute attribute, int value) {
        String name = convertAttribute(attribute);
        if (name != null) {
            download_manager.getDownloadState().setIntAttribute(name, value);
        }
    }

    public long getLongAttribute(TorrentAttribute attribute) {
        String name = convertAttribute(attribute);
        if (name == null) {
            return 0L;
        } // Default value
        return download_manager.getDownloadState().getLongAttribute(name);
    }

    public void setLongAttribute(TorrentAttribute attribute, long value) {
        String name = convertAttribute(attribute);
        if (name != null) {
            download_manager.getDownloadState().setLongAttribute(name, value);
        }
    }

    protected String convertAttribute(TorrentAttribute attribute) {
        if (attribute.getName() == TorrentAttribute.TA_CATEGORY) {

            return (DownloadManagerState.AT_CATEGORY);

        } else if (attribute.getName() == TorrentAttribute.TA_NETWORKS) {

            return (DownloadManagerState.AT_NETWORKS);

        } else if (attribute.getName() == TorrentAttribute.TA_TRACKER_CLIENT_EXTENSIONS) {

            return (DownloadManagerState.AT_TRACKER_CLIENT_EXTENSIONS);

        } else if (attribute.getName() == TorrentAttribute.TA_PEER_SOURCES) {

            return (DownloadManagerState.AT_PEER_SOURCES);

        } else if (attribute.getName() == TorrentAttribute.TA_DISPLAY_NAME) {

            return (DownloadManagerState.AT_DISPLAY_NAME);

        } else if (attribute.getName() == TorrentAttribute.TA_USER_COMMENT) {

            return (DownloadManagerState.AT_USER_COMMENT);

        } else if (attribute.getName() == TorrentAttribute.TA_RELATIVE_SAVE_PATH) {

            return (DownloadManagerState.AT_RELATIVE_SAVE_PATH);

        } else if (attribute.getName() == TorrentAttribute.TA_SHARE_PROPERTIES) {

            // this is a share-level attribute only, not propagated to individual downloads

            return (null);

        } else if (attribute.getName().startsWith("Plugin.")) {

            return (attribute.getName());

        } else {

            Debug.out("Can't convert attribute '" + attribute.getName() + "'");

            return (null);
        }
    }

    protected TorrentAttribute convertAttribute(String name) {
        if (name.equals(DownloadManagerState.AT_CATEGORY)) {

            return (TorrentManagerImpl.getSingleton().getAttribute(TorrentAttribute.TA_CATEGORY));

        } else if (name.equals(DownloadManagerState.AT_NETWORKS)) {

            return (TorrentManagerImpl.getSingleton().getAttribute(TorrentAttribute.TA_NETWORKS));

        } else if (name.equals(DownloadManagerState.AT_PEER_SOURCES)) {

            return (TorrentManagerImpl.getSingleton().getAttribute(TorrentAttribute.TA_PEER_SOURCES));

        } else if (name.equals(DownloadManagerState.AT_TRACKER_CLIENT_EXTENSIONS)) {

            return (TorrentManagerImpl.getSingleton().getAttribute(TorrentAttribute.TA_TRACKER_CLIENT_EXTENSIONS));

        } else if (name.equals(DownloadManagerState.AT_DISPLAY_NAME)) {

            return (TorrentManagerImpl.getSingleton().getAttribute(TorrentAttribute.TA_DISPLAY_NAME));

        } else if (name.equals(DownloadManagerState.AT_USER_COMMENT)) {

            return (TorrentManagerImpl.getSingleton().getAttribute(TorrentAttribute.TA_USER_COMMENT));

        } else if (name.equals(DownloadManagerState.AT_RELATIVE_SAVE_PATH)) {

            return (TorrentManagerImpl.getSingleton().getAttribute(TorrentAttribute.TA_RELATIVE_SAVE_PATH));

        } else if (name.startsWith("Plugin.")) {

            return (TorrentManagerImpl.getSingleton().getAttribute(name));

        } else {

            return (null);
        }
    }

    public void setCategory(String sName) {
        Category category = CategoryManager.getCategory(sName);
        if (category == null)
            category = CategoryManager.createCategory(sName);
        download_manager.getDownloadState().setCategory(category);
    }

    public boolean isPersistent() {
        return download_manager.isPersistent();
    }

    public void remove()

    throws DownloadException, DownloadRemovalVetoException {
        remove(false, false);
    }

    public void remove(boolean delete_torrent, boolean delete_data)

    throws DownloadException, DownloadRemovalVetoException {
        int dl_state = download_manager.getState();

        if (dl_state == DownloadManager.STATE_STOPPED || dl_state == DownloadManager.STATE_ERROR || dl_state == DownloadManager.STATE_QUEUED) {

            GlobalManager globalManager = download_manager.getGlobalManager();

            try {

                globalManager.removeDownloadManager(download_manager, delete_torrent, delete_data);

            } catch (GlobalManagerDownloadRemovalVetoException e) {

                throw (new DownloadRemovalVetoException(e.getMessage()));
            }

        } else {

            throw (new DownloadRemovalVetoException(MessageText.getString("plugin.download.remove.veto.notstopped")));
        }
    }

    public boolean canBeRemoved()

    throws DownloadRemovalVetoException {
        int dl_state = download_manager.getState();

        if (dl_state == DownloadManager.STATE_STOPPED || dl_state == DownloadManager.STATE_ERROR || dl_state == DownloadManager.STATE_QUEUED) {

            GlobalManager globalManager = download_manager.getGlobalManager();

            try {
                globalManager.canDownloadManagerBeRemoved(download_manager, false, false);

            } catch (GlobalManagerDownloadRemovalVetoException e) {

                throw (new DownloadRemovalVetoException(e.getMessage(), e.isSilent()));
            }

        } else {

            throw (new DownloadRemovalVetoException(MessageText.getString("plugin.download.remove.veto.notstopped")));
        }

        return (true);
    }

    public DownloadStats getStats() {
        return (download_stats);
    }

    public boolean isComplete() {
        return download_manager.isDownloadComplete(false);
    }

    public boolean isComplete(boolean bIncludeDND) {
        return download_manager.isDownloadComplete(bIncludeDND);
    }

    public boolean isChecking() {
        return (download_stats.getCheckingDoneInThousandNotation() != -1);
    }

    public boolean isMoving() {
        org.gudy.azureus2.core3.disk.DiskManager dm = download_manager.getDiskManager();

        if (dm != null) {

            return (dm.getMoveProgress() != -1);
        }

        return (false);
    }

    protected void isRemovable() throws DownloadRemovalVetoException {
        // no sync required, see update code

        for (int i = 0; i < removal_listeners.size(); i++) {

            try {
                ((DownloadWillBeRemovedListener) removal_listeners.get(i)).downloadWillBeRemoved(this);

            } catch (DownloadRemovalVetoException e) {

                throw (e);

            } catch (Throwable e) {

                Debug.printStackTrace(e);
            }
        }
    }

    protected void destroy() {
        download_manager.removeListener(this);
    }

    // DownloadManagerListener methods

    public void stateChanged(DownloadManager manager, int state) {
        int prev_state = latest_state;

        latest_state = convertState(state);

        // System.out.println("Plug: dl = " + getName() + ", prev = " + prev_state + ", curr = " + latest_state + ", signalled state = " + state);

        boolean curr_forcedStart = isForceStart();

        // Copy reference in case any attempts to remove or add listeners are tried.
        List listeners_to_use = listeners;

        if (prev_state != latest_state || latest_forcedStart != curr_forcedStart) {

            latest_forcedStart = curr_forcedStart;

            for (int i = 0; i < listeners_to_use.size(); i++) {

                try {
                    long startTime = SystemTime.getCurrentTime();
                    DownloadListener listener = (DownloadListener) listeners_to_use.get(i);

                    listener.stateChanged(this, prev_state, latest_state);

                    long diff = SystemTime.getCurrentTime() - startTime;
                    if (diff > 1000) {
                        System.out.println("Plugin should move long processes (" + diff + "ms) off of Download's stateChanged listener trigger. "
                                + listener);
                    }

                } catch (Throwable e) {

                    Debug.printStackTrace(e);
                }
            }
        }
    }

    public void downloadComplete(DownloadManager manager) {
        if (this.completion_listeners.isEmpty()) {
            return;
        }
        Iterator itr = this.completion_listeners.iterator();
        DownloadCompletionListener dcl;
        while (itr.hasNext()) {
            dcl = (DownloadCompletionListener) itr.next();
            long startTime = SystemTime.getCurrentTime();
            try {
                dcl.onCompletion(this);
            } catch (Throwable t) {
                Debug.printStackTrace(t);
            }
            long diff = SystemTime.getCurrentTime() - startTime;
            if (diff > 1000) {
                System.out.println("Plugin should move long processes (" + diff + "ms) off of Download's onCompletion listener trigger. " + dcl);
            }
        }
    }

    public void completionChanged(DownloadManager manager, boolean bCompleted) {
    }

    public void filePriorityChanged(DownloadManager download, org.gudy.azureus2.core3.disk.DiskManagerFileInfo file) {
    }

    public void positionChanged(DownloadManager download, int oldPosition, int newPosition) {
        for (int i = 0; i < listeners.size(); i++) {
            try {
                long startTime = SystemTime.getCurrentTime();
                DownloadListener listener = (DownloadListener) listeners.get(i);

                listener.positionChanged(this, oldPosition, newPosition);

                long diff = SystemTime.getCurrentTime() - startTime;
                if (diff > 1000) {
                    System.out.println("Plugin should move long processes (" + diff + "ms) off of Download's positionChanged listener trigger. "
                            + listener);
                }
            } catch (Throwable e) {
                Debug.printStackTrace(e);
            }
        }
    }

    public void addListener(DownloadListener l) {
        try {
            listeners_mon.enter();

            List new_listeners = new ArrayList(listeners);

            new_listeners.add(l);

            listeners = new_listeners;
        } finally {

            listeners_mon.exit();
        }
    }

    public void removeListener(DownloadListener l) {
        try {
            listeners_mon.enter();

            List new_listeners = new ArrayList(listeners);

            new_listeners.remove(l);

            listeners = new_listeners;
        } finally {

            listeners_mon.exit();
        }
    }

    public void addAttributeListener(DownloadAttributeListener listener, TorrentAttribute attr, int event_type) {
        String attribute = convertAttribute(attr);
        if (attribute == null) {
            return;
        }

        CopyOnWriteMap attr_map = this.getAttributeMapForType(event_type);
        CopyOnWriteList listener_list = (CopyOnWriteList) attr_map.get(attribute);
        boolean add_self = false;

        if (listener_list == null) {
            listener_list = new CopyOnWriteList();
            attr_map.put(attribute, listener_list);
        }
        add_self = listener_list.isEmpty();

        listener_list.add(listener);
        if (add_self) {
            download_manager.getDownloadState().addListener(this, attribute, event_type);
        }
    }

    public void removeAttributeListener(DownloadAttributeListener listener, TorrentAttribute attr, int event_type) {
        String attribute = convertAttribute(attr);
        if (attribute == null) {
            return;
        }

        CopyOnWriteMap attr_map = this.getAttributeMapForType(event_type);
        CopyOnWriteList listener_list = (CopyOnWriteList) attr_map.get(attribute);
        boolean remove_self = false;

        if (listener_list != null) {
            listener_list.remove(listener);
            remove_self = listener_list.isEmpty();
        }

        if (remove_self) {
            download_manager.getDownloadState().removeListener(this, attribute, event_type);
        }

    }

    public DownloadAnnounceResult getLastAnnounceResult() {
        TRTrackerAnnouncer tc = download_manager.getTrackerClient();

        if (tc != null) {

            last_announce_result.setContent(tc.getLastResponse());
        }

        return (last_announce_result);
    }

    public DownloadScrapeResult getLastScrapeResult() {
        TRTrackerScraperResponse response = download_manager.getTrackerScrapeResponse();

        if (response != null) {

            // don't notify plugins of intermediate (initializing, scraping) states as they would be picked up as errors

            if (response.getStatus() == TRTrackerScraperResponse.ST_ERROR || response.getStatus() == TRTrackerScraperResponse.ST_ONLINE) {

                last_scrape_result.setContent(response);
            }
        }

        return (last_scrape_result);
    }

    public DownloadScrapeResult getAggregatedScrapeResult() {
        List<TRTrackerScraperResponse> responses = download_manager.getGoodTrackerScrapeResponses();

        int best_peers = -1;
        int best_seeds = -1;
        int best_time = -1;

        TRTrackerScraperResponse best_resp = null;

        if (responses != null) {

            for (TRTrackerScraperResponse response : responses) {

                int peers = response.getPeers();
                int seeds = response.getSeeds();

                if (peers > best_peers || (peers == best_peers && seeds > best_seeds)) {

                    best_peers = peers;
                    best_seeds = seeds;

                    best_resp = response;
                }
            }
        }

        // if no good real tracker responses then use less reliable DHT ones

        if (best_peers == -1) {

            try {
                TrackerPeerSource our_dht = null;

                List<TrackerPeerSource> peer_sources = download_manager.getTrackerPeerSources();

                for (TrackerPeerSource ps : peer_sources) {

                    if (ps.getType() == TrackerPeerSource.TP_DHT) {

                        our_dht = ps;

                        break;
                    }
                }

                peer_listeners_mon.enter();

                if (announce_response_map != null) {

                    int total_seeds = 0;
                    int total_peers = 0;
                    int latest_time = 0;

                    int num = 0;

                    if (our_dht != null && our_dht.getStatus() == TrackerPeerSource.ST_ONLINE) {

                        total_seeds = our_dht.getSeedCount();
                        total_peers = our_dht.getLeecherCount();
                        latest_time = our_dht.getLastUpdate();

                        num = 1;
                    }

                    for (int[] entry : announce_response_map.values()) {

                        num++;

                        int seeds = entry[0];
                        int peers = entry[1];
                        int time = entry[3];

                        total_seeds += seeds;
                        total_peers += peers;

                        if (time > latest_time) {

                            latest_time = time;
                        }
                    }

                    if (total_peers >= 0) {

                        best_peers = Math.max(1, total_peers / num);
                        best_seeds = total_seeds / num;

                        if (total_seeds > 0 && best_seeds == 0) {

                            best_seeds = 1;
                        }
                        best_time = latest_time;
                        best_resp = null;
                    }
                }

            } finally {

                peer_listeners_mon.exit();
            }
        }

        if (best_peers >= 0) {

            // System.out.println( download_manager.getDisplayName() + ": " + best_peers + "/" + best_seeds + "/" + best_resp );

            last_aggregate_scrape.update(best_resp, best_seeds, best_peers, best_time);

            return (last_aggregate_scrape);

        } else {

            return (getLastScrapeResult());
        }
    }

    public void scrapeResult(TRTrackerScraperResponse response) {
        // don't notify plugins of intermediate (initializing, scraping) states as they would be picked up as errors
        if (response.getStatus() != TRTrackerScraperResponse.ST_ERROR && response.getStatus() != TRTrackerScraperResponse.ST_ONLINE)
            return;

        last_scrape_result.setContent(response);

        for (int i = 0; i < tracker_listeners.size(); i++) {

            try {
                ((DownloadTrackerListener) tracker_listeners.get(i)).scrapeResult(last_scrape_result);

            } catch (Throwable e) {

                Debug.printStackTrace(e);
            }
        }
    }

    // Used by DownloadEventNotifierImpl.
    void announceTrackerResultsToListener(DownloadTrackerListener l) {
        l.announceResult(last_announce_result);
        l.scrapeResult(last_scrape_result);
    }

    public void announceResult(TRTrackerAnnouncerResponse response) {
        last_announce_result.setContent(response);

        List tracker_listeners_ref = tracker_listeners;

        for (int i = 0; i < tracker_listeners_ref.size(); i++) {

            try {
                ((DownloadTrackerListener) tracker_listeners_ref.get(i)).announceResult(last_announce_result);

            } catch (Throwable e) {

                Debug.printStackTrace(e);
            }
        }
    }

    public TrackerPeerSource getTrackerPeerSource() {
        return (new TrackerPeerSourceAdapter() {
            private long fixup;
            private int state;
            private String details = "";
            private int seeds;
            private int leechers;
            private int peers;

            private void fixup() {
                long now = SystemTime.getCurrentTime();

                if (now - fixup > 1000) {

                    if (!download_manager.getDownloadState().isPeerSourceEnabled(PEPeerSource.PS_PLUGIN)) {

                        state = ST_DISABLED;

                    } else {

                        int s = getState();

                        if (s == ST_DOWNLOADING || s == ST_SEEDING) {

                            state = ST_ONLINE;

                        } else {

                            state = ST_STOPPED;
                        }
                    }

                    if (state == ST_ONLINE) {

                        try {
                            peer_listeners_mon.enter();

                            int s = 0;
                            int l = 0;
                            int p = 0;

                            String str = "";

                            if (announce_response_map != null) {

                                for (Map.Entry<String, int[]> entry : announce_response_map.entrySet()) {

                                    String cn = entry.getKey();
                                    int[] data = entry.getValue();

                                    str += (str.length() == 0 ? "" : ", ") + cn;

                                    str += " " + data[0] + "/" + data[1] + "/" + data[2];

                                    s += data[0];
                                    l += data[1];
                                    p += data[2];
                                }
                            }

                            details = str;

                            if (str.length() == 0) {

                                seeds = -1;
                                leechers = -1;
                                peers = -1;

                            } else {

                                seeds = s;
                                leechers = l;
                                peers = p;
                            }

                        } finally {

                            peer_listeners_mon.exit();
                        }
                    } else {

                        details = "";
                    }

                    fixup = now;
                }
            }

            public int getType() {
                return (TP_PLUGIN);
            }

            public int getStatus() {
                fixup();

                return (state);
            }

            public String getName() {
                fixup();

                if (state == ST_ONLINE) {

                    return (details);
                }

                return ("");
            }

            public int getSeedCount() {
                fixup();

                if (state == ST_ONLINE) {

                    return (seeds);
                }

                return (-1);
            }

            public int getLeecherCount() {
                fixup();

                if (state == ST_ONLINE) {

                    return (leechers);
                }

                return (-1);
            }

            public int getPeers() {
                fixup();

                if (state == ST_ONLINE) {

                    return (peers);
                }

                return (-1);
            }
        });
    }

    private String getTrackingName(Object obj) {
        String name = obj.getClass().getName();

        int pos = name.lastIndexOf('.');

        name = name.substring(pos + 1);

        pos = name.indexOf('$');

        if (pos != -1) {

            name = name.substring(0, pos);
        }

        // hack alert - could use classloader to find plugin I guess

        pos = name.indexOf("DHTTrackerPlugin");

        if (pos == 0) {

            // built in

            name = null;

        } else if (pos > 0) {

            name = name.substring(0, pos);

        } else if (name.equals("DHTAnnounceResult")) {

            name = "mlDHT";
        }

        return (name);
    }

    public void setAnnounceResult(DownloadAnnounceResult result) {
        String class_name = getTrackingName(result);

        if (class_name != null) {

            int seeds = result.getSeedCount();
            int leechers = result.getNonSeedCount();

            DownloadAnnounceResultPeer[] peers = result.getPeers();

            int peer_count = peers == null ? 0 : peers.length;

            try {
                peer_listeners_mon.enter();

                if (announce_response_map == null) {

                    announce_response_map = new HashMap<String, int[]>();

                } else {

                    if (announce_response_map.size() > 32) {

                        Debug.out("eh?");

                        announce_response_map.clear();
                    }
                }

                int[] data = (int[]) announce_response_map.get(class_name);

                if (data == null) {

                    data = new int[4];

                    announce_response_map.put(class_name, data);
                }

                data[0] = seeds;
                data[1] = leechers;
                data[2] = peer_count;
                data[3] = (int) (SystemTime.getCurrentTime() / 1000);

            } finally {

                peer_listeners_mon.exit();
            }
        }

        download_manager.setAnnounceResult(result);
    }

    public void setScrapeResult(DownloadScrapeResult result) {
        String class_name = getTrackingName(result);

        if (class_name != null) {

            int seeds = result.getSeedCount();
            int leechers = result.getNonSeedCount();

            try {
                peer_listeners_mon.enter();

                if (announce_response_map == null) {

                    announce_response_map = new HashMap<String, int[]>();

                } else {

                    if (announce_response_map.size() > 32) {

                        Debug.out("eh?");

                        announce_response_map.clear();
                    }
                }

                int[] data = (int[]) announce_response_map.get(class_name);

                if (data == null) {

                    data = new int[4];

                    announce_response_map.put(class_name, data);
                }

                data[0] = seeds;
                data[1] = leechers;
                data[3] = (int) (SystemTime.getCurrentTime() / 1000);
            } finally {

                peer_listeners_mon.exit();
            }
        }

        download_manager.setScrapeResult(result);
    }

    public void stateChanged(DownloadManagerState state, DownloadManagerStateEvent event) {
        final int type = event.getType();

        if (type == DownloadManagerStateEvent.ET_ATTRIBUTE_WRITTEN || type == DownloadManagerStateEvent.ET_ATTRIBUTE_WILL_BE_READ) {

            String name = (String) event.getData();

            List property_listeners_ref = property_listeners;

            final TorrentAttribute attr = convertAttribute(name);

            if (attr != null) {

                for (int i = 0; i < property_listeners_ref.size(); i++) {

                    try {
                        ((DownloadPropertyListener) property_listeners_ref.get(i)).propertyChanged(this, new DownloadPropertyEvent() {
                            public int getType() {
                                return (type == DownloadManagerStateEvent.ET_ATTRIBUTE_WRITTEN ? DownloadPropertyEvent.PT_TORRENT_ATTRIBUTE_WRITTEN
                                        : DownloadPropertyEvent.PT_TORRENT_ATTRIBUTE_WILL_BE_READ);
                            }

                            public Object getData() {
                                return (attr);
                            }
                        });

                    } catch (Throwable e) {

                        Debug.printStackTrace(e);
                    }
                }
            }
        }
    }

    public void addPropertyListener(DownloadPropertyListener l) {

        // Compatibility for the autostop plugin.
        if ("com.aimedia.stopseeding.core.RatioWatcher".equals(l.getClass().getName())) {

            // Looking at the source code, this method doesn't actually appear to do anything,
            // so we can avoid doing anything for now.
            return;
            /*
             * if (property_to_attribute_map == null) {property_to_attribute_map = new HashMap(1);} DownloadAttributeListener dal = new
             * PropertyListenerBridge(l); property_to_attribute_map.put(l, dal); this.addAttributeListener(dal, attr, event_type);
             */
        }

        PluginDeprecation.call("property listener", l);
        try {
            tracker_listeners_mon.enter();

            List new_property_listeners = new ArrayList(property_listeners);

            new_property_listeners.add(l);

            property_listeners = new_property_listeners;

            if (property_listeners.size() == 1) {

                download_manager.getDownloadState().addListener(this);
            }
        } finally {

            tracker_listeners_mon.exit();
        }
    }

    public void removePropertyListener(DownloadPropertyListener l) {

        // Compatibility for the autostop plugin.
        if ("com.aimedia.stopseeding.core.RatioWatcher".equals(l.getClass().getName())) {

            // Looking at the source code, this method doesn't actually appear to do anything,
            // so we can avoid doing anything for now.
            return;
        }

        try {
            tracker_listeners_mon.enter();

            List new_property_listeners = new ArrayList(property_listeners);

            new_property_listeners.remove(l);

            property_listeners = new_property_listeners;

            if (property_listeners.size() == 0) {

                download_manager.getDownloadState().removeListener(this);
            }
        } finally {

            tracker_listeners_mon.exit();
        }
    }

    public void torrentChanged() {
        TRTrackerAnnouncer client = download_manager.getTrackerClient();

        if (client != null) {

            client.resetTrackerUrl(true);
        }
    }

    public void addTrackerListener(DownloadTrackerListener l) {
        addTrackerListener(l, true);
    }

    public void addTrackerListener(DownloadTrackerListener l, boolean immediateTrigger) {
        try {
            tracker_listeners_mon.enter();

            List new_tracker_listeners = new ArrayList(tracker_listeners);

            new_tracker_listeners.add(l);

            tracker_listeners = new_tracker_listeners;

            if (tracker_listeners.size() == 1) {

                download_manager.addTrackerListener(this);
            }
        } finally {

            tracker_listeners_mon.exit();
        }

        if (immediateTrigger) {
            this.announceTrackerResultsToListener(l);
        }
    }

    public void removeTrackerListener(DownloadTrackerListener l) {
        try {
            tracker_listeners_mon.enter();

            List new_tracker_listeners = new ArrayList(tracker_listeners);

            new_tracker_listeners.remove(l);

            tracker_listeners = new_tracker_listeners;

            if (tracker_listeners.size() == 0) {

                download_manager.removeTrackerListener(this);
            }
        } finally {

            tracker_listeners_mon.exit();
        }
    }

    public void addDownloadWillBeRemovedListener(DownloadWillBeRemovedListener l) {
        try {
            removal_listeners_mon.enter();

            List new_removal_listeners = new ArrayList(removal_listeners);

            new_removal_listeners.add(l);

            removal_listeners = new_removal_listeners;

        } finally {

            removal_listeners_mon.exit();
        }
    }

    public void removeDownloadWillBeRemovedListener(DownloadWillBeRemovedListener l) {
        try {
            removal_listeners_mon.enter();

            List new_removal_listeners = new ArrayList(removal_listeners);

            new_removal_listeners.remove(l);

            removal_listeners = new_removal_listeners;

        } finally {

            removal_listeners_mon.exit();
        }
    }

    public void addPeerListener(final DownloadPeerListener listener) {
        DownloadManagerPeerListener delegate = new DownloadManagerPeerListener() {

            public void peerManagerAdded(PEPeerManager manager) {
                PeerManager pm = PeerManagerImpl.getPeerManager(manager);

                listener.peerManagerAdded(DownloadImpl.this, pm);
            }

            public void peerManagerRemoved(PEPeerManager manager) {
                PeerManager pm = PeerManagerImpl.getPeerManager(manager);

                listener.peerManagerRemoved(DownloadImpl.this, pm);

            }

            public void peerManagerWillBeAdded(PEPeerManager manager) {
            }

            public void peerAdded(PEPeer peer) {
            }

            public void peerRemoved(PEPeer peer) {
            }
        };

        try {
            peer_listeners_mon.enter();

            peer_listeners.put(listener, delegate);

        } finally {

            peer_listeners_mon.exit();
        }

        download_manager.addPeerListener(delegate);
    }

    public void removePeerListener(DownloadPeerListener listener) {
        DownloadManagerPeerListener delegate;

        try {
            peer_listeners_mon.enter();

            delegate = (DownloadManagerPeerListener) peer_listeners.remove(listener);

        } finally {

            peer_listeners_mon.exit();
        }

        if (delegate == null) {

            // sometimes we end up with double removal so don't bother spewing about this
            // Debug.out( "Listener not found for removal" );

        } else {

            download_manager.removePeerListener(delegate);
        }
    }

    public boolean activateRequest(final int count) {
        DownloadActivationEvent event = new DownloadActivationEvent() {
            public Download getDownload() {
                return (DownloadImpl.this);
            }

            public int getActivationCount() {
                return (count);
            }
        };

        for (Iterator it = activation_listeners.iterator(); it.hasNext();) {

            try {
                DownloadActivationListener listener = (DownloadActivationListener) it.next();

                if (listener.activationRequested(event)) {

                    return (true);
                }
            } catch (Throwable e) {

                Debug.printStackTrace(e);
            }
        }

        return (false);
    }

    public DownloadActivationEvent getActivationState() {
        return (activation_state);
    }

    public void addActivationListener(DownloadActivationListener l) {
        try {
            peer_listeners_mon.enter();

            activation_listeners.add(l);

            if (activation_listeners.size() == 1) {

                download_manager.addActivationListener(this);
            }
        } finally {

            peer_listeners_mon.exit();
        }
    }

    public void removeActivationListener(DownloadActivationListener l) {
        try {
            peer_listeners_mon.enter();

            activation_listeners.remove(l);

            if (activation_listeners.size() == 0) {

                download_manager.removeActivationListener(this);
            }
        } finally {

            peer_listeners_mon.exit();
        }
    }

    public void addCompletionListener(DownloadCompletionListener l) {
        try {
            listeners_mon.enter();
            this.completion_listeners.add(l);
        } finally {
            listeners_mon.exit();
        }
    }

    public void removeCompletionListener(DownloadCompletionListener l) {
        try {
            listeners_mon.enter();
            this.completion_listeners.remove(l);
        } finally {
            listeners_mon.exit();
        }
    }

    public PeerManager getPeerManager() {
        PEPeerManager pm = download_manager.getPeerManager();

        if (pm == null) {

            return (null);
        }

        return (PeerManagerImpl.getPeerManager(pm));
    }

    public DiskManager getDiskManager() {
        PeerManager pm = getPeerManager();

        if (pm != null) {

            return (pm.getDiskManager());
        }

        return (null);
    }

    public int getDiskManagerFileCount() {
        return download_manager.getNumFileInfos();
    }

    public DiskManagerFileInfo getDiskManagerFileInfo(int index) {
        org.gudy.azureus2.core3.disk.DiskManagerFileInfo[] info = download_manager.getDiskManagerFileInfo();

        if (info == null) {
            return null;
        }
        if (index < 0 || index >= info.length) {
            return null;
        }

        return new DiskManagerFileInfoImpl(this, info[index]);
    }

    public DiskManagerFileInfo[] getDiskManagerFileInfo() {
        org.gudy.azureus2.core3.disk.DiskManagerFileInfo[] info = download_manager.getDiskManagerFileInfo();

        if (info == null) {

            return (new DiskManagerFileInfo[0]);
        }

        DiskManagerFileInfo[] res = new DiskManagerFileInfo[info.length];

        for (int i = 0; i < res.length; i++) {

            res[i] = new DiskManagerFileInfoImpl(this, info[i]);
        }

        return (res);
    }

    public void setMaximumDownloadKBPerSecond(int kb) {
        if (kb == -1) {
            Debug.out("setMaximiumDownloadKBPerSecond got value (-1) ZERO_DOWNLOAD. (-1)"
                    + "does not work through this method, use getDownloadRateLimitBytesPerSecond() instead.");
        }// if

        download_manager.getStats().setDownloadRateLimitBytesPerSecond(kb < 0 ? 0 : kb * 1024);
    }

    public int getMaximumDownloadKBPerSecond() {
        int bps = download_manager.getStats().getDownloadRateLimitBytesPerSecond();
        return bps <= 0 ? bps : (bps < 1024 ? 1 : bps / 1024);
    }

    public int getUploadRateLimitBytesPerSecond() {
        return download_manager.getStats().getUploadRateLimitBytesPerSecond();
    }

    public void setUploadRateLimitBytesPerSecond(int max_rate_bps) {
        download_manager.getStats().setUploadRateLimitBytesPerSecond(max_rate_bps);
    }

    public int getDownloadRateLimitBytesPerSecond() {
        return download_manager.getStats().getDownloadRateLimitBytesPerSecond();
    }

    public void setDownloadRateLimitBytesPerSecond(int max_rate_bps) {
        download_manager.getStats().setDownloadRateLimitBytesPerSecond(max_rate_bps);
    }

    public void addRateLimiter(RateLimiter limiter, boolean is_upload) {
        download_manager.addRateLimiter(UtilitiesImpl.wrapLimiter(limiter, false), is_upload);
    }

    public void removeRateLimiter(RateLimiter limiter, boolean is_upload) {
        download_manager.removeRateLimiter(UtilitiesImpl.wrapLimiter(limiter, false), is_upload);
    }

    public int getSeedingRank() {
        return download_manager.getSeedingRank();
    }

    public void setSeedingRank(int rank) {
        download_manager.setSeedingRank(rank);
    }

    public String getSavePath() {
        return (download_manager.getSaveLocation().toString());
    }

    public void moveDataFiles(File new_parent_dir)

    throws DownloadException {
        try {
            download_manager.moveDataFiles(new_parent_dir);

        } catch (DownloadManagerException e) {

            throw (new DownloadException("move operation failed", e));
        }
    }

    public void moveDataFiles(File new_parent_dir, String new_name)

    throws DownloadException {
        try {
            download_manager.moveDataFiles(new_parent_dir, new_name);

        } catch (DownloadManagerException e) {

            throw (new DownloadException("move / rename operation failed", e));
        }
    }

    public void renameDownload(String new_name) throws DownloadException {
        try {
            download_manager.renameDownload(new_name);
        } catch (DownloadManagerException e) {
            throw new DownloadException("rename operation failed", e);
        }
    }

    public void moveTorrentFile(File new_parent_dir)

    throws DownloadException {
        try {
            download_manager.moveTorrentFile(new_parent_dir);

        } catch (DownloadManagerException e) {

            throw (new DownloadException("move operation failed", e));
        }
    }

    /**
     * @deprecated
     */
    public File[] calculateDefaultPaths(boolean for_moving) {
        SaveLocationChange slc = this.calculateDefaultDownloadLocation();
        if (slc == null) {
            return null;
        }
        return new File[] { slc.download_location, slc.torrent_location };
    }

    public boolean isInDefaultSaveDir() {
        return download_manager.isInDefaultSaveDir();
    }

    public void requestTrackerAnnounce() {
        download_manager.requestTrackerAnnounce(false);
    }

    public void requestTrackerAnnounce(boolean immediate) {
        download_manager.requestTrackerAnnounce(immediate);
    }

    public void requestTrackerScrape(boolean immediate) {
        download_manager.requestTrackerScrape(immediate);
    }

    public byte[] getDownloadPeerId() {
        TRTrackerAnnouncer announcer = download_manager.getTrackerClient();
        if (announcer == null)
            return null;
        return announcer.getPeerId();
    }

    public boolean isMessagingEnabled() {
        return download_manager.isExtendedMessagingEnabled();
    }

    public void setMessagingEnabled(boolean enabled) {
        throw new RuntimeException(
                "setMessagingEnabled is in the process of being removed - if you are seeing this error, let the Azureus developers know that you need this method to stay!");
        // download_manager.setAZMessagingEnabled( enabled );
    }

    // Deprecated methods

    public int getPriority() {
        return 0;
    }

    public boolean isPriorityLocked() {
        return false;
    }

    public void setPriority(int priority) {
    }

    public boolean isRemoved() {
        return (download_manager.isDestroyed());
    }

    // Pass LogRelation off to core objects

    /*
     * (non-Javadoc)
     * @see org.gudy.azureus2.core3.logging.LogRelation#getLogRelationText()
     */
    public String getRelationText() {
        return propogatedRelationText(download_manager);
    }

    /*
     * (non-Javadoc)
     * @see org.gudy.azureus2.core3.logging.LogRelation#getQueryableInterfaces()
     */
    public Object[] getQueryableInterfaces() {
        return new Object[] { download_manager };
    }

    private CopyOnWriteMap getAttributeMapForType(int event_type) {
        return event_type == DownloadPropertyEvent.PT_TORRENT_ATTRIBUTE_WILL_BE_READ ? read_attribute_listeners_map_cow
                : write_attribute_listeners_map_cow;
    }

    public boolean canMoveDataFiles() {
        return download_manager.canMoveDataFiles();
    }

    public void attributeEventOccurred(DownloadManager download, String attribute, int event_type) {
        CopyOnWriteMap attr_listener_map = getAttributeMapForType(event_type);

        TorrentAttribute attr = convertAttribute(attribute);
        if (attr == null) {
            return;
        }

        List listeners = null;
        listeners = ((CopyOnWriteList) attr_listener_map.get(attribute)).getList();

        if (listeners == null) {
            return;
        }

        for (int i = 0; i < listeners.size(); i++) {
            DownloadAttributeListener dal = (DownloadAttributeListener) listeners.get(i);
            try {
                dal.attributeEventOccurred(this, attr, event_type);
            } catch (Throwable t) {
                Debug.printStackTrace(t);
            }
        }
    }

    public SaveLocationChange calculateDefaultDownloadLocation() {
        return DownloadManagerMoveHandler.recalculatePath(this.download_manager);
    }

    public Object getUserData(Object key) {
        return (download_manager.getUserData(key));
    }

    public void setUserData(Object key, Object data) {
        download_manager.setUserData(key, data);
    }

    public void startDownload(boolean force) {
        if (force) {
            this.setForceStart(true);
            return;
        }
        this.setForceStart(false);

        int state = this.getState();
        if (state == DownloadManager.STATE_STOPPED || state == DownloadManager.STATE_QUEUED) {
            download_manager.setStateWaiting();
        }

    }

    public void stopDownload() {
        if (download_manager.getState() == DownloadManager.STATE_STOPPED) {
            return;
        }
        download_manager.stopIt(DownloadManager.STATE_STOPPED, false, false);
    }

    // stub stuff

    public boolean isStub() {
        return (false);
    }

    public boolean canStubbify() {
        return (manager.canStubbify(this));
    }

    public DownloadStub stubbify()

    throws DownloadException, DownloadRemovalVetoException {
        return (manager.stubbify(this));
    }

    public Download destubbify()

    throws DownloadException {
        return (this);
    }

    public List<DistributedDatabase> getDistributedDatabases() {
        return (DDBaseImpl.getDDBs(this));
    }

    public byte[] getTorrentHash() {
        Torrent t = getTorrent();

        if (t == null) {

            return (null);
        }

        return (t.getHash());
    }

    public long getTorrentSize() {
        Torrent t = getTorrent();

        if (t == null) {

            return (0);
        }

        return (t.getSize());
    }

    public DownloadStubFile[] getStubFiles() {
        DiskManagerFileInfo[] dm_files = getDiskManagerFileInfo();

        DownloadStubFile[] files = new DownloadStubFile[dm_files.length];

        for (int i = 0; i < files.length; i++) {

            final DiskManagerFileInfo dm_file = dm_files[i];

            files[i] = new DownloadStubFile() {
                public File getFile() {
                    return (dm_file.getFile(true));
                }

                public long getLength() {
                    if (dm_file.getDownloaded() == dm_file.getLength() && !dm_file.isSkipped()) {

                        return (dm_file.getLength());

                    } else {

                        return (-dm_file.getLength());
                    }
                }
            };
        }

        return (files);
    }

    public void changeLocation(SaveLocationChange slc) throws DownloadException {

        // No change in the file.
        boolean has_change = slc.hasDownloadChange() || slc.hasTorrentChange();
        if (!has_change) {
            return;
        }

        // Test that one of the locations is actually different.
        has_change = slc.isDifferentDownloadLocation(new File(this.getSavePath()));
        if (!has_change) {
            has_change = slc.isDifferentTorrentLocation(new File(this.getTorrentFileName()));
        }

        if (!has_change) {
            return;
        }

        boolean try_to_resume = !this.isPaused();
        try {
            try {
                if (slc.hasDownloadChange()) {
                    download_manager.moveDataFiles(slc.download_location, slc.download_name);
                }
                if (slc.hasTorrentChange()) {
                    download_manager.moveTorrentFile(slc.torrent_location, slc.torrent_name);
                }
            } catch (DownloadManagerException e) {
                throw new DownloadException(e.getMessage(), e);
            }
        } finally {
            if (try_to_resume) {
                this.resume();
            }
        }

    }

    private static class PropertyListenerBridge implements DownloadAttributeListener {
        private DownloadPropertyListener l;

        public PropertyListenerBridge(DownloadPropertyListener l) {
            this.l = l;
        }

        public void attributeEventOccurred(Download d, final TorrentAttribute attr, final int event_type) {
            l.propertyChanged(d, new DownloadPropertyEvent() {
                public int getType() {
                    return event_type;
                }

                public Object getData() {
                    return attr;
                }
            });
        }
    }

    private static class AggregateScrapeResult implements DownloadScrapeResult {
        private Download dl;

        private TRTrackerScraperResponse response;

        private int seeds;
        private int leechers;

        private int time_secs;

        private AggregateScrapeResult(Download _dl) {
            dl = _dl;
        }

        private void update(TRTrackerScraperResponse _response, int _seeds, int _peers, int _time_secs) {
            response = _response;
            seeds = _seeds;
            leechers = _peers;
            time_secs = _time_secs;
        }

        public Download getDownload() {
            return (dl);
        }

        public int getResponseType() {
            return (RT_SUCCESS);
        }

        public int getSeedCount() {
            return (seeds);
        }

        public int getNonSeedCount() {
            return (leechers);
        }

        public long getScrapeStartTime() {
            TRTrackerScraperResponse r = response;

            if (r != null) {

                return (r.getScrapeStartTime());
            }

            if (time_secs <= 0) {

                return (-1);

            } else {

                return (time_secs * 1000L);
            }
        }

        public void setNextScrapeStartTime(long nextScrapeStartTime) {
            Debug.out("Not Supported");
        }

        public long getNextScrapeStartTime() {
            TRTrackerScraperResponse r = response;

            return (r == null ? -1 : r.getScrapeStartTime());
        }

        public String getStatus() {
            return ("Aggregate Scrape");
        }

        public URL getURL() {
            TRTrackerScraperResponse r = response;

            return (r == null ? null : r.getURL());
        }
    }
}